﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace Techappy.Silverlight.Toolkit
{
    public class CircularPanel3D : Panel
    {
        private Point mouseStart, mouseNow;
        private double destinationDistance = 800;

        public enum AlignmentOptions { Left, Center, Right };

        private const double MouseWheelScaleFactor = 2.5;
        private const double Easing = 0.3;
        private const double MaximumDistance = 3000;
        private const double MinmumDistance = 200;
        private const double MouseEasing = 0.03;

        #region Dependency Properties

        public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.RadiusChanged));
        public static readonly DependencyProperty AngleItemProperty = DependencyProperty.Register("AngleItem", typeof(double), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.AngleItemChanged));
        public static readonly DependencyProperty InitialAngleProperty = DependencyProperty.Register("InitialAngle", typeof(double), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.InitialAngleChanged));
        public static readonly DependencyProperty AlignProperty = DependencyProperty.Register("Align", typeof(AlignmentOptions), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.AlignChanged));
        public static readonly DependencyProperty IsDragOnProperty = DependencyProperty.Register("IsDragOn", typeof(bool), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.IsDragChanged));
        public static readonly DependencyProperty OffsetYProperty = DependencyProperty.Register("OffsetY", typeof(double), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.OffsetYChanged));
        public static readonly DependencyProperty DistanceProperty = DependencyProperty.Register("Distance", typeof(double), typeof(CircularPanel3D), new PropertyMetadata(CircularPanel3D.DistanceChanged));

        private static void RadiusChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel3D)sender).Refresh(); }
        private static void AngleItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel3D)sender).Refresh(); }
        private static void InitialAngleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel3D)sender).Refresh(); }
        private static void AlignChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel3D)sender).Refresh(); }
        private static void IsDragChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel3D)sender).OnIsDragChanged(e); }
        private static void OffsetYChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { ((CircularPanel3D)sender).Refresh(); }
        private static void DistanceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            CircularPanel3D circularPanel3D = (CircularPanel3D)sender;
            circularPanel3D.destinationDistance = (double)e.NewValue;
            circularPanel3D.Refresh();
        }

        [Category("Circular Panel 3D")]
        public double Radius
        {
            get { return (double)this.GetValue(CircularPanel3D.RadiusProperty); }
            set { this.SetValue(CircularPanel3D.RadiusProperty, value); }
        }

        [Category("Circular Panel 3D")]
        public double AngleItem
        {
            get { return (double)this.GetValue(CircularPanel3D.AngleItemProperty); }
            set { this.SetValue(CircularPanel3D.AngleItemProperty, value); }
        }

        [Category("Circular Panel 3D")]
        public double InitialAngle
        {
            get { return (double)this.GetValue(CircularPanel3D.InitialAngleProperty); }
            set { this.SetValue(CircularPanel3D.InitialAngleProperty, value); }
        }

        [Category("Circular Panel 3D")]
        public AlignmentOptions Align
        {
            get { return (AlignmentOptions)this.GetValue(CircularPanel3D.AlignProperty); }
            set { this.SetValue(CircularPanel3D.AlignProperty, value); }
        }

        [Category("Circular Panel 3D")]
        public bool IsDragOn
        {
            get { return (bool)this.GetValue(CircularPanel3D.IsDragOnProperty); }
            set { this.SetValue(CircularPanel3D.IsDragOnProperty, value); }
        }

        [Category("Circular Panel 3D")]
        public double OffsetY
        {
            get { return (double)this.GetValue(CircularPanel3D.OffsetYProperty); }
            set { this.SetValue(CircularPanel3D.OffsetYProperty, value); }
        }

        [Category("Circular Panel 3D")]
        public double Distance
        {
            get { return (double)this.GetValue(CircularPanel3D.DistanceProperty); }
            set { this.SetValue(CircularPanel3D.DistanceProperty, value); }
        }

        #endregion

        private void OnIsDragChanged(DependencyPropertyChangedEventArgs e)
        {
            this.CaptureMouse();
            this.MouseLeftButtonDown += new MouseButtonEventHandler(CircularPanel3D_MouseLeftButtonDown);
            this.MouseLeftButtonUp += new MouseButtonEventHandler(CircularPanel3D_MouseLeftButtonUp);
        }

        void CircularPanel3D_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.CaptureMouse();
            this.mouseStart = e.GetPosition(this);
            this.mouseNow = this.mouseStart;

            CompositionTarget.Rendering += this.ApplyRotationFromMouseMove;
            this.MouseMove += new MouseEventHandler(CircularPanel3D_MouseMove);
        }
        void CircularPanel3D_MouseMove(object sender, MouseEventArgs e)
        {
            CompositionTarget.Rendering += this.ApplyRotationFromMouseMove;
            this.MouseMove -= new MouseEventHandler(this.CircularPanel3D_MouseMove);
        }
        void CircularPanel3D_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.ReleaseMouseCapture();
            this.MouseMove -= new MouseEventHandler(this.CircularPanel3D_MouseMove);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            this.MouseWheel += new MouseWheelEventHandler(HandleMouseWheel);
            CompositionTarget.Rendering += this.ApplyRotationFromMouseMove ;
            Size resultSize = new Size(0, 0);

            foreach (UIElement child in this.Children)
            {
                child.Measure(availableSize);
                resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
                resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
            }

            resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? resultSize.Width : availableSize.Width;
            resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? resultSize.Height : availableSize.Height;

            return resultSize;
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            this.Refresh();
            return base.ArrangeOverride(finalSize);
        }

        private void Refresh()
        {
            int count = 0;
            int col = 0;
            int row = 0;
            int zLevel = 0;

            foreach (FrameworkElement childElement in this.Children)
            {
                double angle = (this.AngleItem * count++) - this.InitialAngle;
                double x = this.Radius * Math.Cos(Math.PI * angle / 180);
                double z = this.Radius * Math.Sin(Math.PI * angle / 180);
                PlaneProjection projection = new PlaneProjection();
                if (projection != null)
                {
                    projection.CenterOfRotationX = 0.5;
                    projection.CenterOfRotationY = 0.5;
                    projection.CenterOfRotationZ = 0.5;

                    projection.RotationY = angle + 90;
                    projection.GlobalOffsetX = x;
                    projection.GlobalOffsetZ = z - this.Distance;
                    projection.GlobalOffsetY = row * (-330) + this.OffsetY;
                }
            }
        }

        public EventHandler ApplyRotationFromMouseMove { get; set; }

        public MouseWheelEventHandler HandleMouseWheel { get; set; }
    }
}
